﻿// @file  ADU.cs
// @author doublecat
// @date   2023-04-25
// @brief
// 
// Copyright (C) 2020  doublecat doublecat@163.com
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see http://www.gnu.org/licenses/.

#region 导入模块

#endregion

#region 导入模块

using System.Collections;

#endregion

namespace ModBus;

/// <summary>
/// ModBus数据包
/// </summary>
public class ADU {
    /// <summary>
    /// 构造一个读命令
    /// </summary>
    /// <param name="sid"></param>
    /// <param name="fid"></param>
    /// <param name="start"></param>
    /// <param name="n"></param>
    public ADU(byte sid, byte fid, ushort start, ushort n) {
        FunCode    = fid;
        SlaveId    = sid;
        Data       = new byte[4] { (byte)(start >> 8), (byte)start, (byte)(n >> 8), (byte)n };
        IsResponse = true;
    }

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="sid">从机ID</param>
    /// <param name="fid">功能函数</param>
    /// <param name="data">字节数据</param>
    /// <param name="isResponse">该数据包是是否是一个命令请求</param>
    public ADU(byte sid, byte fid, ushort[] data, bool isResponse = false) {
        SlaveId = sid;
        FunCode = fid;
        Data    = data.ToByteArray();

        IsResponse  = isResponse;
        IsException = (fid & 0x80) == 0x80;
    }

    /// <summary>
    /// 设置一个线圈
    /// </summary>
    /// <param name="sid">从机ID</param>
    /// <param name="fid">功能代码</param>
    /// <param name="address">线圈地址</param>
    /// <param name="on">线圈状态</param>
    public ADU(byte sid, byte fid, ushort address, bool on)
        : this(sid,
               fid,
               new byte[] {
                              (byte)(address >> 8),
                              (byte)address,
                              (byte)((on) ? (0xFF) : (0x00)),
                              0x00,
                          }) {
    }

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="sid">从机id</param>
    /// <param name="fid">功能id</param>
    /// <param name="address">地址</param>
    /// <param name="value">位数组，离散输入或者线圈</param>
    public ADU(byte sid, byte fid, ushort address, BitArray value) {
        SlaveId = sid;
        FunCode = fid;

        // Data属性包括地址，线圈数量，字节数，字节数组
        int length = (value.Length / 8) + ((value.Length % 8 == 0) ? (0) : (1));
        Data = new byte[length + 5];

        // 地址
        byte[] addressBytes = address.ToByteArray();
        Array.Copy(addressBytes, Data, 2);
        // 线圈数量
        byte[] lengthBytes = ((ushort)value.Length).ToByteArray();
        Array.Copy(lengthBytes, 0, Data, 2, 2);
        // 字节数量
        Data[4] = (byte)length;
        // 字节
        byte[] valueBytes = value.ToByteArray();
        Array.Copy(valueBytes, 0, Data, 5, valueBytes.Length);
    }

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="sid">从机id</param>
    /// <param name="fid">功能id</param>
    /// <param name="data">数据：字节数组</param>
    /// <param name="response">是否响应</param>
    public ADU(byte sid, byte fid, byte[] data, bool response = false) {
        SlaveId    = sid;
        FunCode    = fid;
        Data       = data;
        IsResponse = response;
    }

    /// <summary>
    /// 构造函数，指定寄存器地址和寄存器值
    /// </summary>
    /// <param name="sid">从机id</param>
    /// <param name="fid">功能di</param>
    /// <param name="start">寄存器地址</param>
    /// <param name="value">寄存器值数组</param>
    public ADU(byte sid, byte fid, ushort start, ushort[] value) {
        SlaveId = sid;
        FunCode = fid;

        // 字节数组长度 = 地址（2）+ 长度（2）+ 数据字节长度（1）+ 字节数组长度
        Data = new byte[value.Length * 2 + 5];

        Array.Copy(start.ToByteArray(), Data, 2);

        byte[] lengthBytes = ((ushort)value.Length).ToByteArray();
        Array.Copy(lengthBytes, 0, Data, 2, 2);

        Data[4] = (byte)(value.Length * 2);

        byte[] valueByteArray = value.ToByteArray();
        Array.Copy(valueByteArray, 0, Data, 5, valueByteArray.Length);
    }

    /// <summary>
    /// 数据包是否是异常响应
    /// </summary>
    public bool IsException { get; }

    /// <summary>
    /// 只读，返回CRC校验值
    /// </summary>
    public ushort CRC {
        get {
            byte[] bytes = new byte[2 + Data.Length];
            bytes[0] = SlaveId;
            bytes[1] = FunCode;
            Array.Copy(Data, 0, bytes, 2, Data.Length);
            ushort crc = CalCRC(bytes);
            crc = (ushort)((crc >> 8) | ((crc & 0xFF) << 8));

            return crc;
        }
    }

    /// <summary>
    /// 功能代码
    /// </summary>
    public byte FunCode { get; }

    /// <summary>
    /// 从机地址
    /// </summary>
    public byte SlaveId { get; }

    /// <summary>
    /// 数据
    /// </summary>
    public byte[] Data { get; }

    /// <summary>
    /// RTU是否为请求或者响应
    /// </summary>
    public bool IsResponse { get; }

    /// <summary>
    /// 计算指定字节数组的CRC
    /// </summary>
    /// <param name="bytes"></param>
    /// <returns></returns>
    static ushort CalCRC(byte[] bytes) {
        ushort[] wCRCTable = {
                                 0x0000, 0XC0C1, 0XC181, 0X0140, 0XC301, 0X03C0, 0X0280, 0XC241,
                                 0XC601, 0X06C0, 0X0780, 0XC741, 0X0500, 0XC5C1, 0XC481, 0X0440,
                                 0XCC01, 0X0CC0, 0X0D80, 0XCD41, 0X0F00, 0XCFC1, 0XCE81, 0X0E40,
                                 0X0A00, 0XCAC1, 0XCB81, 0X0B40, 0XC901, 0X09C0, 0X0880, 0XC841,
                                 0XD801, 0X18C0, 0X1980, 0XD941, 0X1B00, 0XDBC1, 0XDA81, 0X1A40,
                                 0X1E00, 0XDEC1, 0XDF81, 0X1F40, 0XDD01, 0X1DC0, 0X1C80, 0XDC41,
                                 0X1400, 0XD4C1, 0XD581, 0X1540, 0XD701, 0X17C0, 0X1680, 0XD641,
                                 0XD201, 0X12C0, 0X1380, 0XD341, 0X1100, 0XD1C1, 0XD081, 0X1040,
                                 0XF001, 0X30C0, 0X3180, 0XF141, 0X3300, 0XF3C1, 0XF281, 0X3240,
                                 0X3600, 0XF6C1, 0XF781, 0X3740, 0XF501, 0X35C0, 0X3480, 0XF441,
                                 0X3C00, 0XFCC1, 0XFD81, 0X3D40, 0XFF01, 0X3FC0, 0X3E80, 0XFE41,
                                 0XFA01, 0X3AC0, 0X3B80, 0XFB41, 0X3900, 0XF9C1, 0XF881, 0X3840,
                                 0X2800, 0XE8C1, 0XE981, 0X2940, 0XEB01, 0X2BC0, 0X2A80, 0XEA41,
                                 0XEE01, 0X2EC0, 0X2F80, 0XEF41, 0X2D00, 0XEDC1, 0XEC81, 0X2C40,
                                 0XE401, 0X24C0, 0X2580, 0XE541, 0X2700, 0XE7C1, 0XE681, 0X2640,
                                 0X2200, 0XE2C1, 0XE381, 0X2340, 0XE101, 0X21C0, 0X2080, 0XE041,
                                 0XA001, 0X60C0, 0X6180, 0XA141, 0X6300, 0XA3C1, 0XA281, 0X6240,
                                 0X6600, 0XA6C1, 0XA781, 0X6740, 0XA501, 0X65C0, 0X6480, 0XA441,
                                 0X6C00, 0XACC1, 0XAD81, 0X6D40, 0XAF01, 0X6FC0, 0X6E80, 0XAE41,
                                 0XAA01, 0X6AC0, 0X6B80, 0XAB41, 0X6900, 0XA9C1, 0XA881, 0X6840,
                                 0X7800, 0XB8C1, 0XB981, 0X7940, 0XBB01, 0X7BC0, 0X7A80, 0XBA41,
                                 0XBE01, 0X7EC0, 0X7F80, 0XBF41, 0X7D00, 0XBDC1, 0XBC81, 0X7C40,
                                 0XB401, 0X74C0, 0X7580, 0XB541, 0X7700, 0XB7C1, 0XB681, 0X7640,
                                 0X7200, 0XB2C1, 0XB381, 0X7340, 0XB101, 0X71C0, 0X7080, 0XB041,
                                 0X5000, 0X90C1, 0X9181, 0X5140, 0X9301, 0X53C0, 0X5280, 0X9241,
                                 0X9601, 0X56C0, 0X5780, 0X9741, 0X5500, 0X95C1, 0X9481, 0X5440,
                                 0X9C01, 0X5CC0, 0X5D80, 0X9D41, 0X5F00, 0X9FC1, 0X9E81, 0X5E40,
                                 0X5A00, 0X9AC1, 0X9B81, 0X5B40, 0X9901, 0X59C0, 0X5880, 0X9841,
                                 0X8801, 0X48C0, 0X4980, 0X8941, 0X4B00, 0X8BC1, 0X8A81, 0X4A40,
                                 0X4E00, 0X8EC1, 0X8F81, 0X4F40, 0X8D01, 0X4DC0, 0X4C80, 0X8C41,
                                 0X4400, 0X84C1, 0X8581, 0X4540, 0X8701, 0X47C0, 0X4680, 0X8641,
                                 0X8201, 0X42C0, 0X4380, 0X8341, 0X4100, 0X81C1, 0X8081, 0X4040
                             };

        ushort wCRCWord = 0xFFFF;
        int    i        = 0;
        int    wLength  = bytes.Length;

        while (wLength-- > 0) {
            byte nTemp = (byte)(bytes[i++] ^ wCRCWord);
            wCRCWord >>= 8;
            wCRCWord ^=  wCRCTable[nTemp];
        }

        return wCRCWord;
    } // End: CRC16

    /// <summary>
    /// 返回RTU字节数组
    /// </summary>
    /// <returns></returns>
    public byte[] ToBytes() {
        List<byte> _bytes = new List<byte>();
        _bytes.Add(SlaveId);
        _bytes.Add(FunCode);
        _bytes.AddRange(Data);

        // 计算CRC
        ushort crc = CalCRC(_bytes.ToArray());
        // CRC低字节在前
        _bytes.Add((byte)(crc));
        _bytes.Add((byte)(crc >> 8));

        return _bytes.ToArray();
    }

    public bool CheckCRC(ushort crc) {
        return CRC == crc;
    }

    public override string ToString() {
        string rs = $@"sid:0x{SlaveId:X2},fid:0x{FunCode:X2},data:{Data.ToHexString()}";
        return rs;
    }
}